package ro.romanescu.useradmin.hibernate;

import java.net.URL;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.codehaus.stax2.XMLEventReader2;
import org.codehaus.stax2.XMLInputFactory2;

import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Restrictions;

import ro.romanescu.useradmin.IRight;
import ro.romanescu.useradmin.IRole;
import ro.romanescu.useradmin.IUser;
import ro.romanescu.useradmin.IUserAttribute;
import ro.romanescu.useradmin.UserAdminException;
import ro.romanescu.useradmin.UserAdminModuleInterface;
import ro.romanescu.useradmin.rights.RightsList;
import ro.romanescu.useradmin.rights.RoleCreateRight;
import ro.romanescu.useradmin.rights.RoleListRight;
import ro.romanescu.useradmin.rights.RoleRemoveRight;


public class HibernateUserAdminModule implements UserAdminModuleInterface {

  private static final Log log = LogFactory.getLog( HibernateUserAdminModule.class );


  /**
   * List of rights which are available within application.
   */
  protected static List<Class> rights = new ArrayList<Class>();


  /**
   * Return description for specific module implementation (for example:
   * "User administration module with LDAP back-end".
   * @return Description of the module functionality/implementation.
   */
  public String getDescription() {
    return "User administration module with Hibernate database backend support";
  }


  /**
   * List all the rights available within framework.
   * @param currentUser Currently logged user to verify its privilege to execute this operation 
   * @return List of rights
   * @throws UserAdminException Exception doing the specified operation
   */
  public List<Class> rightList( IUser currentUser ) throws UserAdminException {
    checkCurrentUserRight( currentUser, RightsList.class );
    return rights;
  }


  /**
   * Retrive the list of roles which have assigned the specified right (table uadm_role_right).
   * @param right Right to check roles for
   * @param currentUser Currently logged user to verify its privilege to execute this operation 
   * @return List of roles
   * @throws UserAdminException
   */
  public List<IRole> rightGetAsigneeRoles( Class right, IUser currentUser ) throws UserAdminException {
    List<IRole> ret = new ArrayList<IRole>();
    if( right == null ) {
      log.warn( "Warning! Passed parameter was null, nothing to do" );
      return ret;
    }
    checkCurrentUserRight( currentUser, RightsList.class, RoleListRight.class );
    try {
      Session session = HibernateDS.openSession();
      List<RoleRight> roles = 
        session.createCriteria( RoleRight.class ).add( Restrictions.eq( "right", right.getName() ) ).list();
      log.debug( String.format( "Query returned %d roles related to right %s", roles.size(), right ) );
      for( RoleRight rr: roles ) {
        IRole r = rr.getRole();
        ret.add( r );
      }
    } catch( HibernateException ex ) {
      log.error( "Database operation error", ex );
      throw new UserAdminException( ex );
    } finally {
      HibernateDS.closeSession();
    }
    return ret;
  }


  /**
   * Create new role (table uadm_role).
   * @param role Role to be added
   * @param currentUser Currently logged user to verify its privilege to execute this operation
   * @return true if operation was successful
   * @throws UserAdminException Exception doing the specified operation
   */
  public IRole roleCreate( IRole role, IUser currentUser ) throws UserAdminException {
    if( role == null) {
      log.warn( "Warning! Passed parameter was null, nothing to do" );
      return null;
    }
    checkCurrentUserRight( currentUser, RoleCreateRight.class );
    Transaction tx = null;
    try {
      Session session = HibernateDS.openSession();
      tx = session.beginTransaction();
      session.save( role );

      List<Class> rights = role.getRights();
      for( Class right: rights ) {
        RoleRight rr = new RoleRight();
        rr.setRight( right.getName() );
        rr.setRole( role );
        session.save( rr );
      }
      tx.commit();
    } catch( HibernateException ex ) {
      if( tx != null )
        tx.rollback();
      log.error( String.format( "Error while saving role %s", role ) );
      throw new UserAdminException( "Database operation error", ex );
    } finally {
      HibernateDS.closeSession();
    }
    return role;
  }

  public void roleRemove( IRole role, IUser currentUser ) throws UserAdminException {
    checkCurrentUserRight( currentUser, RoleRemoveRight.class );
    Transaction tx = null;
    try {
      Session session = HibernateDS.openSession();
      tx = session.beginTransaction();
      session.delete( role );
      // Rights are deleted on cascade as configured by database schema
      tx.commit();
    } catch( HibernateException ex ) {
      if( tx != null )
        tx.rollback();
      log.error( String.format( "Error while removing role %s", role ) );
      throw new UserAdminException( "Database operation error", ex );
    } finally {
      HibernateDS.closeSession();
    }
  }

  public void roleUpdate( IRole role, IUser currentUser ) throws UserAdminException {
  }

  public List<IRole> roleList( IUser currentUser ) throws UserAdminException {
    return null;
  }

  public List<IUser> roleGetAsigneeUser( IRole role, IUser currentUser ) throws UserAdminException {
    return null;
  }

  public void roleAddRight( IRole role, IRight right, IUser currentUser ) throws UserAdminException {
  }

  public void roleRemoveRight( IRole role, IRight right, IUser currentUser ) throws UserAdminException {
  }

  public List<IRight> roleGetRights( IRole role, IUser currentUser ) throws UserAdminException {
    return null;
  }

  public void userCreate( IUser user, IUser currentUser ) throws UserAdminException {
  }

  public void userRemove( IUser user, IUser currentUser ) throws UserAdminException {
  }

  public void userUpdate( IUser user, IUser currentUser ) throws UserAdminException {
  }

  public List<IUser> userList( IUser currentUser ) throws UserAdminException {
    return null;
  }

  public List<IUser> userFind( String searchString, IUser currentUser ) throws UserAdminException {
    return null;
  }

  public void userAddRole( IUser user, IRole role, IUser currentUser ) throws UserAdminException {
  }

  public void userRemoveRole( IUser user, IRole role, IUser currentUser ) throws UserAdminException {
  }

  public List<IRole> userGetRoles( IUser user, IUser currentUser ) throws UserAdminException {
    return null;
  }

  public void userAddAttribute( IUser user, IUserAttribute attribute ) throws UserAdminException {
  }

  public void userRemoveAttribute( IUser user, IUserAttribute attribute ) throws UserAdminException {
  }

  public List<IUserAttribute> userGetAttributes( IUser user, IUser currentUser ) throws UserAdminException {
    return null;
  }

  public void attributeCreate( IUserAttribute attribute, IUser currentUser ) throws UserAdminException {
  }

  public void attributeRemove( IUserAttribute attribute, IUser currentUser ) throws UserAdminException {
  }

  public void attributeUpdate( IUserAttribute attribute, IUser currentUser ) throws UserAdminException {
  }


  /**
   * Configure the library passing the configuration URL.
   * @param configurationFile Path to the configuration file
   */
  public void configure( URL configurationFile ) throws UserAdminException {
    log.info( "Configuring Eximius User Administration Framework" );
    XMLInputFactory2 xmlif = null;
    try {
      xmlif = ( XMLInputFactory2 )XMLInputFactory.newInstance();
      xmlif.setProperty( XMLInputFactory.IS_REPLACING_ENTITY_REFERENCES, Boolean.FALSE );
      xmlif.setProperty( XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE );
      xmlif.setProperty( XMLInputFactory.IS_COALESCING, Boolean.FALSE );
      xmlif.configureForSpeed();

      XMLEventReader2 xmlr = xmlif.createXMLEventReader( configurationFile );

      boolean tEximius = false, tUseradmin = false, tRights = false;
      while( xmlr.hasNextEvent() ) {
        XMLEvent event = xmlr.nextEvent();
        if( event.isStartElement() ) {
          StartElement se = event.asStartElement();
          String tagName = se.getName().toString();
          if( tagName.equals( "eximius" ) ) {
            tEximius = true;
          }
          if( tagName.equals( "useradmin" ) ) {
            tUseradmin = true;
          }
          if( tagName.equals( "rights" ) ) {
            tRights = true;
          }
          if( tEximius && tUseradmin && tRights ) {
            log.debug( "Found User Administration configuration section" );
            if( tagName.equals( "right" ) ) {
              Attribute attr = se.getAttributeByName( new QName( "class" ) );
              String descr = "";
              String val = attr.getValue();
              try {
                Class c = Class.forName( val );
                descr = ( ( IRight )c.newInstance() ).getDescription();
                rights.add( c );
                log.info( "New right found: " + val + "(" + descr + ")" );
              } catch( ClassNotFoundException ex ) {
                log.fatal( "Unable to find listed right: " + val, ex );
              } catch( IllegalAccessException ex ) {
                log.fatal( "Illegal access while instantiating right class: " + val, ex );
              } catch( InstantiationException ex ) {
                log.fatal( "Error while instantiating right class: " + val, ex );
              }
            }
          }
        }
      }
    } catch( XMLStreamException ex ) {
      log.error( "Error while loading dictionary from XLIFF file", ex );
      throw new UserAdminException( ex );
    }
  }


  private static void checkCurrentUserRight( IUser user, Class... rights ) throws UserAdminException {
    if( user == null )
      throw new UserAdminException( "Insuficient privileges for user " + user.getUsername() + 
                                    ".Reason: User is null" );
    List<Class> checkedRights = Arrays.asList( rights );
    List<Class> availableRighs = new ArrayList<Class>();

    List<IRole> roles = user.getRoles();
    for( IRole role: roles ) {
      List<Class> assignedRights = role.getRights();
      availableRighs.addAll( assignedRights );
    }
    if( availableRighs.containsAll( checkedRights ) ) {
      return;
    }
    throw new UserAdminException( String.format( "Insuficient privileges for user %s. Reason:User is missing critical rights: ", 
                                                 user.getUsername(), rights ) );
  }
}
